home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The World of Computer Software
/
The World of Computer Software.iso
/
xstat.lha
/
XStat.mod
< prev
Wrap
Text File
|
1992-09-04
|
46KB
|
1,516 lines
(***************************************************************************
:Program. XStat
:Author. Jⁿrgen Weinelt
:Address. Zur Kanzel 1, D-8783 Hammelburg, Germany
:Address. USENET: uunet.uu.net!cbmvax!cbmehq!cbmger!imart!hcast!jow
:Version. $VER: XStat 1.10 (04-Sep-92)
:Copyright. Freeware
:Language. Modula-2
:Translator. M2Amiga V4.097d
:History. 1.00 initial release
:History. 1.01 bug fix: used to guru when xfer = 0 bytes
:History. 1.02 added peak cps rating
:History. 1.03 added monthly statistics
:History. 1.04 switched to DosL.ReadArgs(), fixed nasty current date
:History. bug with WB2.1 (problems with locale and "TODAY")
:History. 1.05 added per host statistics
:History. 1.06 integrated "per host" statistics into "HOSTNAME" option
:History. (pattern matching using dos.library)
:History. 1.07 bug fix: linked list of Statistics-records was not
:History. sorted correctly
:History. 1.08 added STDERR option; replaced all "°"'s with "av."
:History. 1.09 added "LOCAL" and "IGNORE" to XStat.data
:History. 1.10 added multiple names feature to XStat.data N-lines
:History. fixed a small bug in the NI/NO feature
:Contents. Extract statistics from UUCiCo "Xferstat" logfile. Requires
:Contents. UUCiCo 1.15c (or later) by Andrew "Charly" Kopp
**************************************************************************)
MODULE XStat;
(*$ StackChk:=FALSE *)
(*$ CaseChk:=FALSE *)
(*$ Volatile:=FALSE *)
(*$ DEFINE BETA:=FALSE *)
IMPORT Arts;
IMPORT ASCII;
IMPORT Break;
IMPORT DosD;
IMPORT DosL;
IMPORT ExecD;
IMPORT ExecL;
IMPORT InOut;
IMPORT R;
IMPORT RealConversions;
IMPORT RealInOut;
IMPORT String;
IMPORT SYSTEM;
CONST
strlen=100;
dmult=24*60*60;
hmult=60*60;
mmult=60;
smult=1;
dsun=0*dmult;
dmon=1*dmult;
dtue=2*dmult;
dwed=3*dmult;
dthu=4*dmult;
dfri=5*dmult;
dsat=6*dmult;
defxstatdata="UULIB:XStat.data";
defxferstat="UUSPOOL:XferStat";
vers="XStat 1.10" (*$ IF BETA *) +" *BETA*" (*$ ENDIF *) ;
version="$VER: "+vers+" (04-Sep-92)";
argTemplate="FILE/K,DATA/K,V=VERBOSE/S,M=MONTH/K,NI=NOINCOM/S,NO=NOOUTGO/S,"+
"Q=QUIET/S,H=HOSTNAME/K,FD=FROMDATE/K,TD=TODATE/K,SE=STDERR/S";
extendedhelp="\nXStat "+argTemplate+"\n"+
"\n DATA XStat.data file\t\t def="+defxstatdata+
"\n FILE Xferstat file\t\t def="+defxferstat+
"\n VERBOSE verbose mode switch \t def=off"+
"\n MONTH monthly, override -f/-t\t def=off (format MMM-YY)"+
"\n NOINCOM don't evaluate incoming calls def=off"+
"\n NOOUTGO don't evaluate outgoing calls def=off"+
"\n QUIET suppress non-fatal errors\t def=off"+
"\n HOSTNAME evaluate calls to host\t def=all"+
"\n FROMDATE ignore calls before date\t def=01-JAN-78"+
"\n TODATE ignore calls after date\t def=current date/time"+
"\n STDERR send error messages to StdErr def=off\n\n";
TYPE
StrPtr=POINTER TO ARRAY[0..255] OF CHAR;
DosArgs=
RECORD
file: StrPtr;
data: StrPtr;
verb: LONGINT;
month: StrPtr;
noin: LONGINT;
noout: LONGINT;
quiet: LONGINT;
host: StrPtr;
fdate: StrPtr;
tdate: StrPtr;
stderr: LONGINT;
END;
StringT=ARRAY[0..strlen] OF CHAR;
StringTPtr=POINTER TO StringT;
File=
RECORD
fh: DosD.FileHandlePtr;
eof: BOOLEAN;
END;
CostPtr=POINTER TO Cost;
Cost=
RECORD
next: CostPtr;
start: LONGINT; (* start time for mpu and unit *)
stop: LONGINT; (* end time for mpu and unit *)
mpu: REAL; (* price per unit *)
unit: REAL; (* unit duration *)
END;
XSDataPtr=POINTER TO XSData;
XSData=
RECORD
next: XSDataPtr;
host: StringT; (* host name *)
local: BOOLEAN; (* count transmissions, no cost, no units *)
ignore: BOOLEAN; (* ignore this host entirely *)
double: BOOLEAN; (* if true, don't free the attached costlist *)
costlist: CostPtr; (* list of Cost records *)
END;
Connect=
RECORD
host: StringT; (* host name *)
callout: BOOLEAN; (* TRUE: outgoing call *)
start: LONGINT; (* session start time *)
stop: LONGINT; (* session end time *)
sdate: DosD.Date; (* session start time in dos format *)
inbb: LONGINT; (* brutto bytes in *)
outbb: LONGINT; (* brutto bytes out *)
inbn: LONGINT; (* netto bytes in *)
outbn: LONGINT; (* netto bytes out *)
cost: REAL; (* connection phone costs *)
units: LONGINT; (* phone units consumed *)
seconds: LONGINT; (* online time in seconds *)
local: BOOLEAN; (* was local -> no cost, no units *)
ignore: BOOLEAN; (* do not count this at all *)
END;
StatisticsPtr=POINTER TO Statistics;
Statistics=
RECORD
next: StatisticsPtr;
name: StringT;
in: BOOLEAN;
connects: LONGINT;
inbb: LONGINT;
outbb: LONGINT;
inbn: LONGINT;
outbn: LONGINT;
cost: REAL;
units: LONGINT;
seconds: LONGINT;
bpeak: LONGINT;
npeak: LONGINT;
localconnects: LONGINT;
ignoreconnects: LONGINT;
END;
Args=
RECORD
xstatdata: StringT; (* xstat.data name and path; *)
xferstat: StringT; (* xferstat name and path; *)
verbose: BOOLEAN; (* lotsa output; ; off *)
from: DosD.Date; (* from date; ; zero *)
to: DosD.Date; (* to date; ; TODAY *)
host: ARRAY[0..400] OF CHAR; (* host name pattern; ; all *)
in: BOOLEAN; (* process incoming calls; ; on *)
out: BOOLEAN; (* process outgoing calls; ; on *)
quiet: BOOLEAN; (* suppress non-fatal errors; ; off *)
monthly: BOOLEAN; (* monthly mode; ; off *)
perhost: BOOLEAN; (* perhost mode; ; off *)
stderr: BOOLEAN; (* stderr mode; ; off *)
END;
VAR
inf: File;
ln1,ln2,temp: StringT;
log: Connect;
totali,totalo: Statistics;
xsroot: XSDataPtr;
args: Args;
currency: StringT;
rdargsPtr: DosD.RDArgsPtr;
statroot: StatisticsPtr;
index: INTEGER;
stdErr: DosD.FileHandlePtr;
(*---------------------------------------------------------------------------
FreeCostChain: FreeMem() a linked list of "Cost" records
---------------------------------------------------------------------------*)
PROCEDURE FreeCostChain(d:CostPtr);
VAR
n: CostPtr;
BEGIN
REPEAT
n:=d^.next;
ExecL.FreeMem(d,SIZE(d^));
d:=n;
UNTIL n=NIL;
END FreeCostChain;
(*---------------------------------------------------------------------------
FreeStat: FreeMem() a linked list of "Statistics" records
---------------------------------------------------------------------------*)
PROCEDURE FreeStat(s: StatisticsPtr);
VAR
n: StatisticsPtr;
BEGIN
(* some memory may be lost if an error occurs while the XStat.data file *)
(* is being parsed... that's because the n^.tmp field is not checked *)
(* here. the problem is, checking n^.tmp might cause a "memory freed *)
(* twice" guru under certain conditions. losing a few bytes seems like *)
(* the smaller evil in this case. *)
REPEAT
n:=s^.next;
ExecL.FreeMem(s,SIZE(s^));
s:=n;
UNTIL n=NIL;
END FreeStat;
(*---------------------------------------------------------------------------
FreeXData: FreeMem() a linked list of "XSData" records, freeing the
attached "Cost" record lists as well (if double=FALSE)
---------------------------------------------------------------------------*)
PROCEDURE FreeXData(d: XSDataPtr);
VAR
n: XSDataPtr;
BEGIN
REPEAT
n:=d^.next;
IF (d^.costlist#NIL) AND (NOT d^.double) THEN
FreeCostChain(d^.costlist);
END;
ExecL.FreeMem(d,SIZE(d^));
d:=n;
UNTIL n=NIL;
END FreeXData;
(*---------------------------------------------------------------------------
MyError: Display error messages, terminate program if necessary
redirect errors to STDERR if requested by new 1.08 cli switch
---------------------------------------------------------------------------*)
(*$ CopyDyn:=FALSE *)
PROCEDURE MyError(x1,x2,x3: ARRAY OF CHAR; fatal: BOOLEAN);
VAR
lidummy: LONGINT;
BEGIN
IF stdErr#NIL THEN
IF fatal THEN
lidummy:=DosL.Write(stdErr,SYSTEM.ADR("FATAL ERROR "),12);
lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x1),String.Length(x1));
lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\o"),1);
lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x2),String.Length(x2));
lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\o"),1);
lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x3),String.Length(x3));
lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\n"),2);
Arts.Exit(DosD.fail);
ELSIF (NOT args.quiet) OR ((x1[0]="F") & (x1[1]="A") & (x1[2]="T")) THEN
lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x1),String.Length(x1));
lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\o"),1);
lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x2),String.Length(x2));
lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\o"),1);
lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x3),String.Length(x3));
lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\n"),2);
END;
ELSE
IF fatal THEN
InOut.WriteString("FATAL ERROR ");
InOut.WriteString(x1); InOut.WriteLn;
InOut.WriteString(x2); InOut.WriteLn;
InOut.WriteString(x3); InOut.WriteLn;
InOut.WriteLn;
Arts.Exit(DosD.fail);
ELSIF (NOT args.quiet) OR ((x1[0]="F") & (x1[1]="A") & (x1[2]="T")) THEN
InOut.WriteString(x1); InOut.WriteLn;
InOut.WriteString(x2); InOut.WriteLn;
InOut.WriteString(x3); InOut.WriteLn;
InOut.WriteLn;
END;
END;
END MyError;
(*---------------------------------------------------------------------------
GetHead: Remove first char from a string, return it in char variable
---------------------------------------------------------------------------*)
PROCEDURE GetHead(VAR s: StringT; VAR c: CHAR);
BEGIN
c:=s[0];
String.DeleteChar(s,0);
END GetHead;
(*---------------------------------------------------------------------------
KillSpaces: Remove leading spaces from a string
---------------------------------------------------------------------------*)
PROCEDURE KillSpaces(VAR l1: StringT);
VAR
c: CHAR;
BEGIN
WHILE l1[0]=" " DO
GetHead(l1,c);
Break.TestBreak();
END;
END KillSpaces;
(*---------------------------------------------------------------------------
Get: Remove first "word" from a string. "Word" means "a sequence of
non-spaces, terminated by at least one space". Return this word
as a separate string
---------------------------------------------------------------------------*)
PROCEDURE Get(VAR s1,s2: StringT): BOOLEAN;
VAR
c: CHAR;
BEGIN
s2[0]:=ASCII.nul;
KillSpaces(s1);
GetHead(s1,c);
WHILE (c#" ") AND (c#ASCII.nul) DO
Break.TestBreak();
String.ConcatChar(s2,c);
GetHead(s1,c);
END;
RETURN String.Length(s2)>0;
END Get;
(*---------------------------------------------------------------------------
ConvDate: Convert ASCII representations of date and time to Dos format
---------------------------------------------------------------------------*)
(*$ CopyDyn:=FALSE *)
PROCEDURE ConvDate(ds,ts: ARRAY OF CHAR; VAR dat: DosD.Date): BOOLEAN;
VAR
dt: DosD.DateTime;
res: LONGINT;
BEGIN
dt.format:=DosD.formatDOS;
dt.flags:=DosD.DateTimeFlagSet{};
dt.strDate:=SYSTEM.ADR(ds);
dt.strTime:=SYSTEM.ADR(ts);
res:=DosL.StrToDate(SYSTEM.ADR(dt));
dat:=dt.date;
RETURN res=0;
END ConvDate;
(*---------------------------------------------------------------------------
ParseDosArgs: Parse argument string and set "args" record accordingly
---------------------------------------------------------------------------*)
PROCEDURE ParseDosArgs(VAR s: StringT);
VAR
ts: StringT;
dargs: DosArgs;
rc: SYSTEM.ADDRESS;
pres: LONGINT;
BEGIN
rdargsPtr:=DosL.AllocDosObject(DosD.dosRdArgs,NIL);
IF rdargsPtr=NIL THEN
MyError("in command line parser",
"not enough memory",
"can't parse command line arguments",TRUE);
END;
rdargsPtr^.source.buffer:=SYSTEM.ADR(s);
rdargsPtr^.source.length:=String.Length(s);
rdargsPtr^.source.curChr:=0;
rdargsPtr^.daList:=0;
rdargsPtr^.buffer:=NIL;
rdargsPtr^.bufSiz:=0;
rdargsPtr^.extHelp:=SYSTEM.ADR(extendedhelp);
rdargsPtr^.flags:=DosD.RDAFlagSet{};
IF DosL.ReadArgs(SYSTEM.ADR(argTemplate),SYSTEM.ADR(dargs),rdargsPtr)=NIL THEN
MyError("in command line parser","illegal command line",s,TRUE);
END;
IF dargs.file#NIL THEN
String.Copy(args.xferstat,dargs.file^);
END;
IF dargs.data#NIL THEN
String.Copy(args.xstatdata,dargs.data^);
END;
args.verbose:=(dargs.verb#0);
IF dargs.fdate#NIL THEN
IF ConvDate(dargs.fdate^,"00:00:00",args.from) THEN
MyError("in command line","illegal FDATE",dargs.fdate^,TRUE);
END;
END;
IF dargs.tdate#NIL THEN
IF ConvDate(dargs.tdate^,"23:59:59",args.to) THEN
MyError("in command line","illegal TDATE",dargs.tdate^,TRUE);
END;
END;
IF dargs.host#NIL THEN
pres:=DosL.ParsePatternNoCase(dargs.host,
SYSTEM.ADR(args.host),
SIZE(args.host)-2); (* just being cautious :-) *)
IF pres<0 THEN
MyError("in command line","pattern in HOST too complex",dargs.host^,TRUE);
END;
args.perhost:=(pres=1);
END;
IF args.in THEN
args.in:=(dargs.noin=0);
END;
IF args.out THEN
args.out:=(dargs.noout=0);
END;
args.quiet:=args.quiet OR (dargs.quiet#0);
args.stderr:=(dargs.stderr#0);
IF dargs.month#NIL THEN
ts:="01-";
String.Concat(ts,dargs.month^);
IF ConvDate(ts,"00:00:00",args.from) THEN
MyError("in command line","illegal MONTH",dargs.month^,TRUE);
END;
ts:="31-";
String.Concat(ts,dargs.month^);
IF ConvDate(ts,"23:59:59",args.to) THEN
ts:="30-";
String.Concat(ts,dargs.month^);
IF ConvDate(ts,"23:59:59",args.to) THEN
ts:="29-";
String.Concat(ts,dargs.month^);
IF ConvDate(ts,"23:59:59",args.to) THEN
ts:="28-";
String.Concat(ts,dargs.month^);
IF ConvDate(ts,"23:59:59",args.to) THEN
MyError("in command line","illegal MONTH",dargs.month^,TRUE);
END;
END;
END;
END;
args.monthly:=TRUE;
END;
IF rdargsPtr#NIL THEN
DosL.FreeArgs(rdargsPtr);
DosL.FreeDosObject(DosD.dosRdArgs,rdargsPtr);
rdargsPtr:=NIL;
END;
END ParseDosArgs;
(*---------------------------------------------------------------------------
PresetArgs: Set default values for "args" record
---------------------------------------------------------------------------*)
PROCEDURE PresetArgs();
VAR
bdummy: BOOLEAN;
BEGIN
args.xstatdata:=defxstatdata;
args.xferstat:=defxferstat;
args.verbose:=FALSE;
bdummy:=ConvDate("01-JAN-78","00:00:00",args.from);
DosL.DateStamp(SYSTEM.ADR(args.to));
args.host[0]:=ASCII.nul;
args.in:=TRUE;
args.out:=TRUE;
args.quiet:=FALSE;
args.monthly:=FALSE;
END PresetArgs;
(*---------------------------------------------------------------------------
GetLine: Read a line from the input file
---------------------------------------------------------------------------*)
PROCEDURE GetLine(VAR line: StringT);
VAR
res: LONGINT;
BEGIN
IF inf.fh#NIL THEN
res:=DosL.FGets(inf.fh,SYSTEM.ADR(line),strlen);
inf.eof:=(res=0) AND (DosL.IoErr()=0);
END;
IF (String.Length(line)>0) AND (line[String.Length(line)-1]=ASCII.lf) THEN
line[String.Length(line)-1]:=ASCII.nul;
END;
END GetLine;
(*---------------------------------------------------------------------------
GetLog: Read a two-line log entry from Xferstat
---------------------------------------------------------------------------*)
PROCEDURE GetLog(VAR l1,l2: StringT);
BEGIN
l1[0]:=ASCII.nul;
l2[0]:=ASCII.nul;
REPEAT
Break.TestBreak();
GetLine(l1);
UNTIL (inf.eof) OR (l1[0]="<") OR (l1[0]=">");
IF NOT inf.eof THEN
GetLine(l2);
END;
END GetLog;
(*---------------------------------------------------------------------------
Skip: Skip the first "word" of a string; refer to "Get()" for an
explanation ot the term "word"
---------------------------------------------------------------------------*)
PROCEDURE Skip(VAR s: StringT);
VAR
dum: BOOLEAN;
x: StringT;
BEGIN
dum:=Get(s,x);
END Skip;
(*---------------------------------------------------------------------------
GetLNum: Get the first "word" of a string, and convert it to a LONGINT
(if possible). Refer to "Get()" for an explanation of the
term "word"
---------------------------------------------------------------------------*)
PROCEDURE GetLNum(VAR s: StringT; VAR x: LONGINT): BOOLEAN;
VAR
sn: StringT;
res: LONGINT;
BEGIN
IF Get(s,sn) THEN
res:=DosL.StrToLong(SYSTEM.ADR(sn),x);
RETURN res>0;
END;
RETURN FALSE;
END GetLNum;
(*---------------------------------------------------------------------------
GetNum: Call "GetLNum()" and make sure the result is an INTEGER
---------------------------------------------------------------------------*)
PROCEDURE GetNum(VAR s: StringT; VAR x: INTEGER): BOOLEAN;
VAR
l: LONGINT;
BEGIN
IF GetLNum(s,l) THEN
IF l>MAX(INTEGER) THEN
x:=0;
ELSE
x:=INTEGER(l);
RETURN TRUE;
END;
ELSE
x:=0;
END;
RETURN FALSE;
END GetNum;
(*---------------------------------------------------------------------------
GetReal: Get the first "word" of a string, and convert it to a REAL
(if possible). Refer to "Get()" for an explanation of the
term "word"
---------------------------------------------------------------------------*)
PROCEDURE GetReal(VAR s: StringT; VAR x: REAL): BOOLEAN;
VAR
sn: StringT;
err: BOOLEAN;
BEGIN
IF Get(s,sn) THEN
RealConversions.StrToReal(sn,x,err);
RETURN NOT err;
END;
RETURN FALSE;
END GetReal;
(*---------------------------------------------------------------------------
GetTime: Get the first two "words" of a string, and convert them to
Dos format date and time (if possible). Refer to "Get()" for an
explanation of the term "word"
---------------------------------------------------------------------------*)
PROCEDURE GetTime(VAR s: StringT; VAR t: LONGINT; VAR dos: DosD.Date): BOOLEAN;
VAR
ds,ts: StringT;
dt: DosD.DateTime;
BEGIN
IF Get(s,ds) THEN
IF Get(s,ts) THEN
dt.format:=DosD.formatCDN;
dt.flags:=DosD.DateTimeFlagSet{};
dt.strDate:=SYSTEM.ADR(ds);
dt.strTime:=SYSTEM.ADR(ts);
IF DosL.StrToDate(SYSTEM.ADR(dt))#0 THEN
dos:=dt.date;
t:=(dos.days MOD 7)*dmult+(dos.minute*mmult)+(dos.tick DIV 50);
RETURN TRUE;
END;
END;
END;
RETURN FALSE;
END GetTime;
(*---------------------------------------------------------------------------
TestXStatHeader: Check if the input file's header is "H XSTAT DATA"
---------------------------------------------------------------------------*)
PROCEDURE TestXStatHeader();
VAR
s1,s,t: StringT;
c: CHAR;
BEGIN
GetLine(s);
s1:=s;
GetHead(s,c);
IF c="H" THEN
IF Get(s,t) OR (String.Compare(t,"XSTAT")#0) THEN
IF NOT (Get(s,t) OR (String.Compare(t,"DATA")#0)) THEN
MyError("in XStat.data header",
s1,"keyword DATA not found",TRUE);
END;
ELSE
MyError("in XStat.data header",
s1,"keyword XSTAT not found",TRUE);
END;
ELSE
MyError("in XStat.data header",
s1,"header identifier H not found",TRUE);
END;
END TestXStatHeader;
(*---------------------------------------------------------------------------
ParseXSDataNEntry: Parse a "host name" line from XStat.data
---------------------------------------------------------------------------*)
PROCEDURE ParseXSDataNEntry(VAR s,s1: StringT; VAR ccxsd: XSDataPtr);
VAR
cxsd: XSDataPtr;
xsd: XSData;
t: StringT;
first: BOOLEAN;
BEGIN
IF (ccxsd#NIL) AND (ccxsd^.costlist=NIL) AND (NOT ccxsd^.local) AND (NOT ccxsd^.ignore) THEN
MyError("in XStat.data",
"N-line without subsequent C-lines for",
ccxsd^.host,TRUE);
END;
first:=TRUE;
IF Get(s,t) THEN
REPEAT
xsd.next:=NIL;
xsd.host:=t;
xsd.costlist:=NIL;
xsd.local:=FALSE;
xsd.ignore:=FALSE;
xsd.double:=FALSE;
cxsd:=xsroot;
IF xsroot#NIL THEN
WHILE cxsd^.next#NIL DO
Break.TestBreak();
cxsd:=cxsd^.next;
END;
cxsd^.next:=ExecL.AllocMem(SIZE(xsd),ExecD.MemReqSet{});
IF cxsd^.next=NIL THEN
MyError("","not enough memory","",TRUE);
END;
cxsd:=cxsd^.next;
IF first THEN
first:=FALSE;
ccxsd:=cxsd;
ELSE
xsd.double:=TRUE;
END;
ELSE
xsroot:=ExecL.AllocMem(SIZE(xsd),ExecD.MemReqSet{});
IF xsroot=NIL THEN
MyError("","not enough memory","",TRUE);
END;
cxsd:=xsroot;
ccxsd:=cxsd;
first:=FALSE;
END;
cxsd^:=xsd;
UNTIL NOT Get(s,t);
ELSE
MyError("in XStat.data",
s1,"no host name found in N-line",TRUE);
END;
END ParseXSDataNEntry;
(*---------------------------------------------------------------------------
ParseXSDataCEntry: Parse a "cost info" line from XStat.data
---------------------------------------------------------------------------*)
PROCEDURE ParseXSDataCEntry(VAR s,s1: StringT; VAR cxsd: XSDataPtr);
VAR
t,t1: StringT;
cost: Cost;
ccp: CostPtr;
temp: INTEGER;
c0{R.D7},c1{R.D6}: CHAR;
BEGIN
IF cxsd=NIL THEN
MyError("in XStat.data",s1,"C-line without corresponding N-line",TRUE);
END;
cost.next:=NIL;
cost.stop:=7*dmult-1;
IF NOT Get(s,t) THEN
MyError("in XStat.data",s1,"illegal or missing start time in C-line",TRUE);
END;
IF String.Compare(t,"LOCAL")=0 THEN
IF cxsd^.costlist#NIL THEN
MyError("in XStat.data",s1,"LOCAL must be the only C-line for the host",TRUE);
ELSIF cxsd^.local#FALSE THEN
MyError("in XStat.data",s1,"duplicate LOCAL after N-line",TRUE);
ELSIF cxsd^.ignore#FALSE THEN
MyError("in XStat.data",s1,"LOCAL and IGNORE are mutually exclusive",TRUE);
ELSE
cxsd^.local:=TRUE;
END;
ELSIF String.Compare(t,"IGNORE")=0 THEN
IF cxsd^.costlist#NIL THEN
MyError("in XStat.data",s1,"IGNORE must be the only C-line for the host",TRUE);
ELSIF cxsd^.ignore#FALSE THEN
MyError("in XStat.data",s1,"duplicate IGNORE after N-line",TRUE);
ELSIF cxsd^.local#FALSE THEN
MyError("in XStat.data",s1,"LOCAL and IGNORE are mutually exclusive",TRUE);
ELSE
cxsd^.ignore:=TRUE;
END;
ELSIF (t[2]="-") AND (t[5]=":") AND (t[8]=":") AND (String.Length(t)=11) THEN
c0:=t[0]; c1:=t[1];
IF (c0="S") AND (c1="U") THEN
cost.start:=dsun;
ELSIF (c0="M") AND (c1="O") THEN
cost.start:=dmon;
ELSIF (c0="T") AND (c1="U") THEN
cost.start:=dtue;
ELSIF (c0="W") AND (c1="E") THEN
cost.start:=dwed;
ELSIF (c0="T") AND (c1="H") THEN
cost.start:=dthu;
ELSIF (c0="F") AND (c1="R") THEN
cost.start:=dfri;
ELSIF (c0="S") AND (c1="A") THEN
cost.start:=dsat;
ELSE
MyError("in XStat.data",s1,"illegal day of week in start time in C-line",TRUE);
END;
t[0]:=" "; t[1]:=" "; t[2]:=" "; t[5]:=" "; t[8]:=" ";
IF NOT GetNum(t,temp) THEN
MyError("in XStat.data",s1,"illegal 'hours' part in start time in C-line",TRUE);
END;
cost.start:=cost.start+hmult*LONGINT(temp);
IF NOT GetNum(t,temp) THEN
MyError("in XStat.data",s1,"illegal 'minutes' part in start time in C-line",TRUE);
END;
cost.start:=cost.start+mmult*LONGINT(temp);
IF NOT GetNum(t,temp) THEN
MyError("in XStat.data",s1,"illegal 'seconds' part in start time in C-line",TRUE);
END;
cost.start:=cost.start+smult*LONGINT(temp);
IF NOT GetReal(s,cost.unit) THEN
MyError("in XStat.data",s1,"illegal 'seconds per unit' in C-line",TRUE);
END;
IF NOT GetReal(s,cost.mpu) THEN
MyError("in XStat.data",s1,"illegal 'money per unit' in C-line",TRUE);
END;
IF (cxsd^.local) OR (cxsd^.ignore) THEN
MyError("in XStat.data",s1,"no more C-lines allowed after LOCAL or IGNORE",TRUE);
END;
ccp:=cxsd^.costlist;
IF ccp#NIL THEN
WHILE ccp^.next#NIL DO
Break.TestBreak();
ccp:=ccp^.next;
END;
ccp^.next:=ExecL.AllocMem(SIZE(Cost),ExecD.MemReqSet{});
IF ccp^.next=NIL THEN
MyError("","not enough memory","",TRUE);
END;
ccp^.next^:=cost;
ccp^.stop:=cost.start-1;
IF ccp^.stop<ccp^.start THEN
MyError("in XStat.data","connect start time sequence error",
"(you probably swapped some C-lines, or you left out an N-line)",TRUE);
END;
ELSE
cxsd^.costlist:=ExecL.AllocMem(SIZE(Cost),ExecD.MemReqSet{});
IF cxsd^.costlist=NIL THEN
MyError("","not enough memory","",TRUE);
END;
cxsd^.costlist^:=cost;
IF cost.start#0 THEN
MyError("in XStat.data",s1,"start time in first C-line must be SU-00:00:00",TRUE);
END;
END;
ELSE
MyError("in XStat.data",s1,"illegal or missing start time in C-line",TRUE);
END;
(* cxsd will always be #NIL here... *)
WHILE cxsd^.next#NIL DO
(* copy costlistPtr to next^.costlistPtr *)
cxsd^.next^.costlist:=cxsd^.costlist;
cxsd^.next^.local:=cxsd^.local;
cxsd^.next^.ignore:=cxsd^.ignore;
(* advance cxsd to cxsd#.next; note: cxsd is a VAR parameter, so this *)
(* will only happen for the first C-record found... from then on, *)
(* this costlistPtr copying will not be done again. *)
cxsd:=cxsd^.next;
END;
END ParseXSDataCEntry;
(*---------------------------------------------------------------------------
ParseXSDataSEntry: Parse a "currency sign info" line from XStat.data
---------------------------------------------------------------------------*)
PROCEDURE ParseXSDataSEntry(VAR s,s1: StringT; VAR cxsd: XSDataPtr);
VAR
t: StringT;
BEGIN
IF (cxsd#NIL) THEN
MyError("in XStat.data",
s1,"S-line after the first connection cost record",TRUE);
END;
IF Get(s,t) THEN
IF currency[0]=ASCII.nul THEN
currency:=t;
IF currency[0]#" " THEN
String.Insert(currency,0," \o");
END;
ELSE
MyError("in XStat.data",
s1,"duplicate S-line found",TRUE);
END;
ELSE
MyError("in XStat.data",
s1,"missing currency sign in S-line",TRUE);
END;
END ParseXSDataSEntry;
(*---------------------------------------------------------------------------
GetXStatData: Read and parse the XStat.data file
---------------------------------------------------------------------------*)
PROCEDURE GetXStatData();
VAR
s1,s: StringT;
c: CHAR;
cxsd: XSDataPtr;
BEGIN
cxsd:=NIL;
TestXStatHeader();
REPEAT
Break.TestBreak();
GetLine(s);
s1:=s;
GetHead(s,c);
CASE c OF
|"#": (* NOP *)
|"N": ParseXSDataNEntry(s,s1,cxsd);
|"C": ParseXSDataCEntry(s,s1,cxsd);
|"S": ParseXSDataSEntry(s,s1,cxsd);
|ELSE (* unknown: NOP *)
END;
UNTIL inf.eof;
END GetXStatData;
(*---------------------------------------------------------------------------
ParseLog: Parse a two-line connection data entry from Xferstat
---------------------------------------------------------------------------*)
(*$ CopyDyn:=FALSE *)
PROCEDURE ParseLog(line1,line2: StringT; VAR log: Connect): BOOLEAN;
VAR
c: CHAR;
l1,l2: StringT;
dummy: DosD.Date;
BEGIN
l1:=line1;
l2:=line2;
GetHead(l1,c);
log.callout:=(c="<");
IF Get(l1,log.host) THEN
IF (log.host[0]>="0") AND (log.host[0]<="9") THEN
MyError("invalid log entry: can't find host name",line1,line2,FALSE);
RETURN FALSE;
END;
IF GetTime(l1,log.start,log.sdate) THEN
Skip(l1);
IF GetTime(l1,log.stop,dummy) THEN
GetHead(l2,c);
IF c="|" THEN
Skip(l2);
Skip(l2);
Skip(l2);
IF GetLNum(l2,log.inbb) THEN
IF GetLNum(l2,log.outbb) THEN
Skip(l2);
IF GetLNum(l2,log.inbn) THEN
IF GetLNum(l2,log.outbn) THEN
RETURN TRUE;
ELSE
MyError("invalid log entry: can't get netto send bytes",line1,line2,FALSE);
END;
ELSE
MyError("invalid log entry: can't get netto receive bytes",line1,line2,FALSE);
END;
ELSE
MyError("invalid log entry: can't get brutto send bytes",line1,line2,FALSE);
END;
ELSE
MyError("invalid log entry: can't get brutto receive bytes",line1,line2,FALSE);
END;
ELSE
MyError("invalid log entry: illegal continuation line header ",line1,line2,FALSE);
END;
ELSE
MyError("invalid log entry: can't compute end time",line1,line2,FALSE);
END;
ELSE
MyError("invalid log entry: can't compute start time",line1,line2,FALSE);
END;
ELSE
MyError("invalid log entry: can't find host name",line1,line2,FALSE);
END;
RETURN FALSE;
END ParseLog;
(*---------------------------------------------------------------------------
ComputeCost: Compute the cost for a single connect
---------------------------------------------------------------------------*)
PROCEDURE ComputeCost(VAR log: Connect): BOOLEAN;
VAR
cxsd: XSDataPtr;
ccost: CostPtr;
time: REAL;
BEGIN
log.seconds:=log.stop-log.start+1;
IF log.seconds<0 THEN
log.seconds:=log.seconds+7*dmult;
END;
cxsd:=xsroot;
WHILE (cxsd#NIL) AND (String.Compare(cxsd^.host,log.host)#0) DO
Break.TestBreak();
cxsd:=cxsd^.next;
END;
IF cxsd=NIL THEN
MyError("ERROR: no connection cost data found for host",log.host,
"extend your XStat.data file!",FALSE);
RETURN FALSE;
END;
IF NOT (cxsd^.local OR cxsd^.ignore) THEN
ccost:=cxsd^.costlist;
WHILE log.start>ccost^.stop DO
Break.TestBreak();
ccost:=ccost^.next;
END;
END;
IF log.stop<log.start THEN
log.stop:=log.stop+7*dmult;
END;
log.cost:=0.0;
log.units:=0;
IF NOT (cxsd^.local OR cxsd^.ignore) THEN
time:=REAL(log.start);
REPEAT
Break.TestBreak();
log.cost:=log.cost+ccost^.mpu;
time:=time+ccost^.unit;
INC(log.units);
IF LONGINT(time)>ccost^.stop THEN
ccost:=ccost^.next;
IF ccost=NIL THEN
ccost:=cxsd^.costlist;
time:=time-REAL(7*dmult);
log.stop:=log.stop-7*dmult;
END;
END;
UNTIL LONGINT(time)>log.stop;
END;
log.local:=cxsd^.local;
log.ignore:=cxsd^.ignore;
RETURN TRUE;
END ComputeCost;
(*---------------------------------------------------------------------------
DisplayConnect: Display the statistics for a single connect
---------------------------------------------------------------------------*)
PROCEDURE DisplayConnect(log: Connect);
VAR
name: StringT;
BEGIN
name:=log.host;
WHILE String.Length(name)<8 DO
Break.TestBreak();
String.Insert(name,0," \o");
END;
InOut.WriteString("\nhost name\t"); InOut.WriteString(name);
InOut.WriteString("\ndirection\t "); IF log.callout THEN
InOut.WriteString("out");
ELSE
InOut.WriteString(" in");
END;
InOut.WriteString("\nonline time\t"); InOut.WriteInt(log.seconds,8); InOut.WriteString(" seconds");
InOut.WriteString("\nunits\t\t");
IF log.local THEN
InOut.WriteString("LOCAL");
ELSIF log.ignore THEN
InOut.WriteString("IGNORE");
ELSE
InOut.WriteInt(log.units,8); InOut.WriteString(" units");
END;
InOut.WriteString("\ncost\t\t");
IF log.local THEN
InOut.WriteString("LOCAL");
ELSIF log.ignore THEN
InOut.WriteString("IGNORE");
ELSE
RealInOut.WriteReal(log.cost,8,2); InOut.Write(" "); InOut.WriteString(currency);
END;
InOut.WriteString("\n\n\n");
END DisplayConnect;
(*---------------------------------------------------------------------------
DisplayStats: Display the overall statistics
---------------------------------------------------------------------------*)
PROCEDURE DisplayStats(tot: Statistics);
BEGIN
InOut.WriteString("Connection statistics for ");
IF NOT tot.in THEN
InOut.WriteString("outgoing");
ELSE
InOut.WriteString("incoming");
END;
IF tot.name[0]="\o" THEN
InOut.WriteString(" calls:\n");
InOut.WriteString("-----------------------------------------\n");
ELSE
InOut.WriteString(" calls ");
IF NOT tot.in THEN
InOut.WriteString("to");
ELSE
InOut.WriteString("from");
END;
InOut.WriteString(' host "');
InOut.WriteString(tot.name);
InOut.WriteString('":\n');
END;
IF tot.connects=0 THEN
InOut.WriteString("\nno connects recorded.\n\n\n");
ELSE
InOut.WriteString("\nconnects "); InOut.WriteInt(tot.connects,8); InOut.WriteString(" \t ("); InOut.WriteInt(tot.localconnects,0); InOut.WriteString(" of them were local)");
IF tot.name[0]="\o" THEN
InOut.WriteString("\nignored connects"); InOut.WriteInt(tot.ignoreconnects,8);
END;
InOut.WriteString("\nonline time "); InOut.WriteInt(tot.seconds,8); InOut.WriteString(" sec\t ("); InOut.WriteInt((tot.seconds+tot.connects-1) DIV tot.connects,8); InOut.WriteString(" sec/connect)");
InOut.WriteString("\nunits "); InOut.WriteInt(tot.units,8); InOut.WriteString(" units\t ("); RealInOut.WriteReal(REAL(tot.units)/REAL(tot.connects),8,3); InOut.WriteString(" units/connect)");
InOut.WriteString("\ncost "); RealInOut.WriteReal(tot.cost,8,2); InOut.WriteString(currency); InOut.WriteString("\t ("); RealInOut.WriteReal(tot.cost/REAL(tot.connects),8,3); InOut.WriteString(currency); InOut.WriteString("/connect)");
InOut.WriteString("\n\nbrutto read "); InOut.WriteInt(tot.inbb,8); InOut.WriteString(" bytes\t ("); InOut.WriteInt((tot.inbb+tot.connects-1) DIV tot.connects,8); InOut.WriteString(" bytes/connect)");
InOut.WriteString("\nbrutto send "); InOut.WriteInt(tot.outbb,8); InOut.WriteString(" bytes\t ("); InOut.WriteInt((tot.outbb+tot.connects-1) DIV tot.connects,8); InOut.WriteString(" bytes/connect)");
InOut.WriteString("\nnetto read "); InOut.WriteInt(tot.inbn,8); InOut.WriteString(" bytes\t ("); InOut.WriteInt((tot.inbn+tot.connects-1) DIV tot.connects,8); InOut.WriteString(" bytes/connect)");
InOut.WriteString("\nnetto send "); InOut.WriteInt(tot.outbn,8); InOut.WriteString(" bytes\t ("); InOut.WriteInt((tot.outbn+tot.connects-1) DIV tot.connects,8); InOut.WriteString(" bytes/connect)\n");
InOut.WriteString("\nav. brutto speed"); InOut.WriteInt((tot.inbb+tot.outbb) DIV tot.seconds,8); InOut.WriteString(" cps\t ("); InOut.WriteInt(tot.bpeak,8); InOut.WriteString(" cps peak)");
InOut.WriteString("\nav. netto speed "); InOut.WriteInt((tot.inbn+tot.outbn) DIV tot.seconds,8); InOut.WriteString(" cps\t ("); InOut.WriteInt(tot.npeak,8); InOut.WriteString(" cps peak)");
InOut.WriteString("\nav. brutto cost ");
IF tot.inbb+tot.outbb=0 THEN
InOut.WriteString(" -");
ELSE
RealInOut.WriteReal(tot.cost/REAL(tot.inbb+tot.outbb)*1048576.0,8,3);
END;
InOut.WriteString(currency); InOut.WriteString("/MB");
InOut.WriteString("\nav. netto cost ");
IF tot.inbn+tot.outbn=0 THEN
InOut.WriteString(" -");
ELSE
RealInOut.WriteReal(tot.cost/REAL(tot.inbn+tot.outbn)*1048576.0,8,3);
END;
InOut.WriteString(currency); InOut.WriteString("/MB\n\n\n");
END;
END DisplayStats;
(*---------------------------------------------------------------------------
UpdateStat: Update the totali/totalo records
---------------------------------------------------------------------------*)
PROCEDURE UpdateStat(VAR tot: Statistics; VAR log: Connect);
VAR
tempspeed: LONGINT;
BEGIN
IF NOT log.ignore THEN
INC(tot.connects);
tot.seconds:=tot.seconds+log.seconds;
tot.inbb:=tot.inbb+log.inbb;
tot.outbb:=tot.outbb+log.outbb;
tot.inbn:=tot.inbn+log.inbn;
tot.outbn:=tot.outbn+log.outbn;
tot.units:=tot.units+log.units;
tot.cost:=tot.cost+log.cost;
IF log.seconds#0 THEN
tempspeed:=(log.inbb+log.outbb) DIV log.seconds;
IF tempspeed>tot.bpeak THEN tot.bpeak:=tempspeed END;
tempspeed:=(log.inbn+log.outbn) DIV log.seconds;
IF tempspeed>tot.npeak THEN tot.npeak:=tempspeed END;
END;
END;
IF log.ignore THEN
INC(tot.ignoreconnects);
END;
IF log.local THEN
INC(tot.localconnects);
END;
END UpdateStat;
(*---------------------------------------------------------------------------
ClearStat: Preset a Statistics-record with zeros
---------------------------------------------------------------------------*)
PROCEDURE ClearStat(VAR stat: Statistics);
BEGIN
stat.next:=NIL;
stat.name:="\o\o";
stat.in:=FALSE;
stat.connects:=0;
stat.inbb:=0;
stat.outbb:=0;
stat.inbn:=0;
stat.outbn:=0;
stat.cost:=0.0;
stat.units:=0;
stat.seconds:=0;
stat.bpeak:=0;
stat.npeak:=0;
stat.localconnects:=0;
stat.ignoreconnects:=0;
END ClearStat;
(*---------------------------------------------------------------------------
InsertStat: Insert a Statistics-record at the proper position
into the linked list
---------------------------------------------------------------------------*)
PROCEDURE InsertStat(stat: StatisticsPtr);
VAR
cur,prev: StatisticsPtr;
done: BOOLEAN;
BEGIN
done:=FALSE;
IF statroot=NIL THEN
statroot:=stat;
ELSE
cur:=statroot;
prev:=NIL;
REPEAT
IF String.Compare(stat^.name,cur^.name)=0 THEN
IF stat^.in THEN
(* insert before current *)
IF prev=NIL THEN
stat^.next:=statroot;
statroot:=stat;
done:=TRUE;
ELSE
stat^.next:=prev^.next;
prev^.next:=stat;
done:=TRUE;
END;
ELSE
(* insert after current *)
stat^.next:=cur^.next;
cur^.next:=stat;
done:=TRUE;
END;
ELSIF String.Compare(stat^.name,cur^.name)<0 THEN
(* insert before current *)
IF prev=NIL THEN
stat^.next:=statroot;
statroot:=stat;
done:=TRUE;
ELSE
stat^.next:=prev^.next;
prev^.next:=stat;
done:=TRUE;
END;
ELSE
prev:=cur;
cur:=cur^.next;
END;
Break.TestBreak();
UNTIL (cur=NIL) OR done;
IF cur=NIL THEN
prev^.next:=stat;
END;
END;
END InsertStat;
(*---------------------------------------------------------------------------
FindStat: Search linked list for a specific Statistics-record
Create that record if not found
---------------------------------------------------------------------------*)
PROCEDURE FindStat(name: StringT; in: BOOLEAN): StatisticsPtr;
VAR
cur,next: StatisticsPtr;
found: BOOLEAN;
BEGIN
next:=statroot;
IF next#NIL THEN
REPEAT
cur:=next;
next:=cur^.next;
found:=(in=cur^.in) AND (String.Compare(name,cur^.name)=0);
Break.TestBreak();
UNTIL found OR (next=NIL);
ELSE
found:=FALSE;
END;
IF found THEN
RETURN cur;
ELSE
next:=ExecL.AllocMem(SIZE(Statistics),ExecD.MemReqSet{});
IF next=NIL THEN
MyError("","not enough memory","",TRUE);
END;
ClearStat(next^);
next^.name:=name;
next^.in:=in;
InsertStat(next);
RETURN next;
END;
END FindStat;
(*---------------------------------------------------------------------------
DisplayStatList: Display the entire list of Statistics-records
---------------------------------------------------------------------------*)
PROCEDURE DisplayStatList;
VAR
cur: StatisticsPtr;
BEGIN
cur:=statroot;
WHILE cur#NIL DO
IF (cur^.in AND args.in) OR ((NOT cur^.in) AND args.out) THEN
DisplayStats(cur^);
END;
cur:=cur^.next;
END;
END DisplayStatList;
BEGIN
SYSTEM.SETREG(11,SYSTEM.ADR(version));
rdargsPtr:=NIL;
xsroot:=NIL;
statroot:=NIL;
inf.fh:=NIL;
stdErr:=NIL;
IF (Arts.kickVersion<37) OR (DosL.dosVersion<37) THEN
MyError("","You need OS 2.04 or later (dos.library V37 or later)",
"",TRUE);
END;
Break.InstallException;
PresetArgs();
InOut.WriteString("\n\n"+vers+"\n");
InOut.WriteString("⌐ Copyright 1992 by Jⁿrgen Weinelt\n");
(*$ IF BETA *)
InOut.WriteString("THIS IS A BETA VERSION! DO NOT DISTRIBUTE!\n\n");
(*$ ELSE *)
InOut.WriteString("XStat is Freeware - read the docs for details.\n\n");
(*$ ENDIF *)
IF NOT Arts.wbStarted THEN
IF Arts.dosCmdLen>0 THEN
temp:=SYSTEM.CAST(StringTPtr,Arts.dosCmdBuf)^;
IF temp[0]="?" THEN
InOut.WriteString(extendedhelp);
Arts.Exit(5);
END;
END;
END;
IF DosL.GetVar(SYSTEM.ADR("XSTATARGS"),SYSTEM.ADR(temp),
strlen,DosD.VarFlagSet{})#-1 THEN
index:=String.Length(temp);
IF temp[index-1]#"\n" THEN
temp[index]:="\n"; temp[index+1]:="\o";
END;
ParseDosArgs(temp);
END;
IF NOT Arts.wbStarted THEN
IF Arts.dosCmdLen>0 THEN
temp:=SYSTEM.CAST(StringTPtr,Arts.dosCmdBuf)^;
ParseDosArgs(temp);
END;
END;
IF DosL.CompareDates(SYSTEM.ADR(args.from),SYSTEM.ADR(args.to))<0 THEN
MyError("","illegal combination of parameters",
"FROMDATE is later than TODATE",TRUE);
END;
IF NOT (args.in OR args.out) THEN
MyError("","illegal combination of parameters",
"you can't specify NOINCOM and NOOUTGO simultaneously",TRUE);
END;
IF args.stderr THEN
stdErr:=DosL.Open(SYSTEM.ADR("CONSOLE:"),DosD.newFile);
IF stdErr=NIL THEN
MyError("ERROR:","can't open CONSOLE: for StdErr output",
"guess you'll have to live without it :-)",FALSE);
END;
END;
ClearStat(totali);
totali.in:=TRUE;
ClearStat(totalo);
totalo.in:=FALSE;
inf.fh:=DosL.Open(SYSTEM.ADR(args.xstatdata),DosD.readOnly);
IF inf.fh=NIL THEN
MyError("Can't open XStat.data!","filename was",args.xstatdata,TRUE);
END;
GetXStatData();
DosL.Close(inf.fh);
IF currency[0]=ASCII.nul THEN
MyError("WARNING","no currency definition found in XStat.data",
"using 'DM' as default",FALSE);
currency:=" DM";
END;
inf.fh:=DosL.Open(SYSTEM.ADR(args.xferstat),DosD.readOnly);
IF inf.fh=NIL THEN
MyError("Can't open Xferstat!","filename was",args.xferstat,TRUE);
END;
REPEAT
Break.TestBreak();
GetLog(ln1,ln2);
IF String.Length(ln1)+String.Length(ln2)>0 THEN
IF ParseLog(ln1,ln2,log) THEN
IF (args.host[0]=ASCII.nul) OR
(DosL.MatchPatternNoCase(SYSTEM.ADR(args.host),SYSTEM.ADR(log.host))) THEN
IF (DosL.CompareDates(SYSTEM.ADR(args.from),SYSTEM.ADR(log.sdate))>=0) AND
(DosL.CompareDates(SYSTEM.ADR(log.sdate),SYSTEM.ADR(args.to))>=0) THEN
IF ComputeCost(log) THEN
IF args.verbose THEN
DisplayConnect(log);
END;
IF args.perhost THEN
IF NOT log.ignore THEN
UpdateStat(FindStat(log.host,NOT log.callout)^,log);
END;
END;
IF log.callout THEN
UpdateStat(totalo,log);
ELSE
UpdateStat(totali,log);
END;
ELSE
IF NOT args.quiet THEN
InOut.WriteString("(ignoring this one)\n\n\n");
END;
END;
END;
END;
ELSE
IF NOT args.quiet THEN
InOut.WriteString("(ignoring this one)\n\n\n");
END;
END;
END;
UNTIL inf.eof;
IF args.perhost THEN
DisplayStatList;
END;
IF args.in THEN
DisplayStats(totali);
END;
IF args.out THEN
DisplayStats(totalo);
END;
CLOSE
IF rdargsPtr#NIL THEN
DosL.FreeArgs(rdargsPtr);
DosL.FreeDosObject(DosD.dosRdArgs,rdargsPtr);
rdargsPtr:=NIL;
END;
IF xsroot#NIL THEN
FreeXData(xsroot);
xsroot:=NIL;
END;
IF statroot#NIL THEN
FreeStat(statroot);
statroot:=NIL;
END;
IF inf.fh#NIL THEN
DosL.Close(inf.fh);
inf.fh:=NIL;
END;
IF stdErr#NIL THEN
DosL.Close(stdErr);
stdErr:=NIL;
END;
END XStat.